package com.toobigtofail.utility
{
	import Box2D.Common.Math.b2Vec2;
	
	import com.toobigtofail.entities.Enemy;
	import com.toobigtofail.entities.Obstacle;
	import com.toobigtofail.entities.Target;
	import com.toobigtofail.placeholders.*;
	import com.toobigtofail.shapes.*;
	
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	import net.flashpunk.FP;
	
	public class Level
	{
		// This is weird. We have to reference all of the classes that we're going to load dynamically
		// otherwise flash won't compile those classes and we'll get a 1065 error.
		// So when we add a new placeholder, we have to reference it here.
		private static const _allPlaceholders:Array = [PlankPlaceholder, WedgePlaceholder];
		
		private var _xml:XML;
		private var _hasParsedXml:Boolean = false;
		
		private var _enemies:Array;
		private var _targets:Array;
		private var _placeholders:Array;
		private var _obstacles:Array;
		private var _drawPower:Number;
		
		private var _buildAreaTL:b2Vec2; // top-left corner of the build area
		private var _buildAreaWidth:int;
		private var _buildAreaHeight:int;
		
		private var _xM:Number = 1;
		private var _yM:Number = 1;
		
		public function Level(xml:XML) {
			_xml = xml;			
		}
		
		public function reset():void {
			_hasParsedXml = false;
			parseXml();
		}
		
		private function parseXml():void {
			if (_hasParsedXml) return;
			
			var shape:IShape;
			
			_xM = FP.screen.width / _xml.@width;
			_yM = FP.screen.height / _xml.@height;
			
			if (_xml.buildArea[0]) {
				_buildAreaTL = new b2Vec2(_xml.buildArea[0].@x * _xM, _xml.buildArea[0].@y * _yM);
				_buildAreaWidth = _xml.buildArea[0].@width * _xM;
				_buildAreaHeight = _xml.buildArea[0].@height * _yM;
			}
			
			if (_xml.draw[0]) {
				_drawPower = _xml.draw[0].@power; 
			}
			else {
				_drawPower = 0;
			}
			
			_enemies = new Array;
			if (_xml.enemies[0]) {
				for each (var enemy:XML in _xml.enemies[0].enemy) {
					shape = parseShape(enemy.shape[0]);
					if (null != shape) {
						_enemies.push(new Enemy(shape, new b2Vec2(enemy.@goX * _xM, enemy.@goY * _yM)));
					}
				}
			}
			
			_targets = new Array;
			if (_xml.targets[0]) {
				for each (var target:XML in _xml.targets[0].target) {
					shape = parseShape(target.shape[0]);
					if (null != shape) {
						_targets.push(new Target(shape));
					}
				}
			}
			
			_placeholders = new Array;
			if (_xml.buildingBlocks[0]) {
				for each (var placeholder:XML in _xml.buildingBlocks[0].placeholder) {
					var ClassRef:Class = getDefinitionByName("com.toobigtofail.placeholders::" + placeholder.@type) as Class;
					for (var i:int = 0; i < placeholder.@count; i++) {
						_placeholders.push(new ClassRef(0,0) as Placeholder);
					}
				}
			}
			
			_obstacles = new Array;
			if (_xml.obstacles[0]) {
				for each (var obstacle:XML in _xml.obstacles[0].obstacle) {
					shape = parseShape(obstacle.shape[0]);
					if (null != shape) {
						_obstacles.push(new Obstacle(shape, obstacle.@angle));
					}
				}
			}
			
			_hasParsedXml = true;
		}
		
		private function parseShape(shapeDef:XML):IShape {
			var type:String = shapeDef.@type;
			switch (type) {
				case "Ball":
					return new Ball(shapeDef.@x * _xM, shapeDef.@y * _yM, shapeDef.@radius, shapeDef.@b2Type);
					break;
				case "Box":
					return new Box(shapeDef.@x * _xM, shapeDef.@y * _yM, shapeDef.@width, shapeDef.@height, shapeDef.@b2Type);
					break;
				case "Octagon":
					return new Octagon(shapeDef.@x * _xM, shapeDef.@y * _yM, shapeDef.@radius, shapeDef.@b2Type);
					break;
				case "Wedge":
					return new Wedge(shapeDef.@x * _xM, shapeDef.@y * _yM, shapeDef.@width, shapeDef.@height, shapeDef.@b2Type);
					break;
				case "Line":
					var verts:Array = new Array;
					for each (var point:XML in shapeDef.point) {
						verts.push(new b2Vec2(point.@x, point.@y));
					}
					return new Line(shapeDef.@x * _xM, shapeDef.@y * _yM, verts, shapeDef.@b2Type);
					break;
				default:
					trace("ERROR: Unknown shape type [" + shapeDef.@type + "]");
					return null;
			}
		}
		
		public function serialize():XML {
			if (!_hasParsedXml) return _xml;
			
			var level:XML = <level/>;
			
			level.@width = FP.width;
			level.@height = FP.height;
			
			var ba:XML = <buildArea/>;
			ba.@x = _buildAreaTL.x;
			ba.@y = _buildAreaTL.y;
			ba.@width = _buildAreaWidth;
			ba.@height = _buildAreaHeight;
			level.appendChild(ba);
			
			var da:XML = <draw/>;
			da.@power = this._drawPower;
			level.appendChild(da);
			
			var xenemies:XML = <enemies/>;
			for each (var e:Enemy in _enemies) {
				xenemies.appendChild(e.serialize());
			}
			level.appendChild(xenemies);
			
			var xtargets:XML = <targets/>;
			for each (var t:Target in _targets) {
				xtargets.appendChild(t.serialize());
			}
			level.appendChild(xtargets);
			
			var xobstacles:XML = <obstacles/>;
			for each (var o:Obstacle in _obstacles) {
				xobstacles.appendChild(o.serialize());
			}
			level.appendChild(xobstacles);
			
			var xbuildingBlocks:XML = <buildingBlocks/>;
			var d:Dictionary = new Dictionary;
			for each (var p:Placeholder in _placeholders) {
				var name:String = getQualifiedClassName(p);
				if (!d[name]) { d[name] = 1; }
				else { d[name] += 1; }
			}
			
			for (var key:String in d) {
				var xp:XML = <placeholder/>;
				xp.@type = key.split("::").pop();;
				xp.@count = d[key];
				xbuildingBlocks.appendChild(xp);
			}
			level.appendChild(xbuildingBlocks);
			
			return level;
		}
		
		public function get enemies():Array 	 { parseXml(); return _enemies; }
		public function get targets():Array 	 { parseXml(); return _targets; }
		public function get placeholders():Array { parseXml(); return _placeholders; }
		public function get obstacles():Array    { parseXml(); return _obstacles; }
		
		public function get drawPower():Number { parseXml(); return _drawPower; }
		
		public function get buildAreaTopLeft():b2Vec2 { parseXml(); return _buildAreaTL; }
		public function get buildAreaWidth():Number   { parseXml(); return _buildAreaWidth; }
		public function get buildAreaHeight():Number  { parseXml(); return _buildAreaHeight; }
		
		public function set xml(xml:XML):void { 
			_xml = xml;
			_hasParsedXml = false;
		}
	}
}